const bwrPalette = [
  [0, 0, 0, 255],
  [255, 255, 255, 255],
  [255, 0, 0, 255]
]

const bwPalette = [
  [0, 0, 0, 255],
  [255, 255, 255, 255],
]



function hexToRGB(h) {
  let r = 0,
    g = 0,
    b = 0;
  // 3 digits
  if (h.length == 4) {
    r = "0x" + h[1] + h[1];
    g = "0x" + h[2] + h[2];
    b = "0x" + h[3] + h[3];
    // 6 digits
  } else if (h.length == 7) {
    r = "0x" + h[1] + h[2];
    g = "0x" + h[3] + h[4];
    b = "0x" + h[5] + h[6];
  }

  return {
    r: +r,
    g: +g,
    b: +b
  };
}

function RGBToHSL(R, G, B) {
  let r = R / 255,
    g = G / 255,
    b = B / 255,
    cmin = Math.min(r, g, b),
    cmax = Math.max(r, g, b),
    delta = cmax - cmin,
    hue = 0,
    sat = 0,
    light = 0;
  // get hue
  if (delta == 0)
    hue = 0;
  else if (cmax == r)
    hue = ((g - b) / delta) % 6;
  else if (cmax == g)
    hue = (b - r) / delta + 2;
  else
    hue = (r - g) / delta + 4;

  hue = Math.round(hue * 60);

  if (hue < 0)
    hue += 360;
  // get lightness and saturation
  light = (cmax + cmin) / 2;
  sat = delta == 0 ? 0 : delta / (1 - Math.abs(2 * light - 1));

  sat = Math.round(sat * 100);
  light = Math.round(light * 100);

  return {
    h: hue,
    s: sat,
    l: light
  };
}



function getColorDistance(rgba1, rgba2) {
  const [r1, b1, g1] = rgba1;
  const [r2, b2, g2] = rgba2;

  const rm = (r1 + r2) / 2;

  const r = r1 - r2;
  const g = g1 - g2;
  const b = b1 - b2;

  return Math.sqrt((2 + rm / 256) * r * r + 4 * g * g + (2 + (255 - rm) / 256) * b * b);
}

function getNearColor(pixel, palette) {
  let minDistance = 255 * 255 * 3 + 1;
  let paletteIndex = 0;

  for (let i = 0; i < palette.length; i++) {
    const targetColor = palette[i];
    const distance = getColorDistance(pixel, targetColor);
    if (distance < minDistance) {
      minDistance = distance;
      paletteIndex = i;
    }
  }

  return palette[paletteIndex];
}


function getNearColorV2(color, palette) {
  let minDistanceSquared = 255 * 255 + 255 * 255 + 255 * 255 + 1;

  let bestIndex = 0;
  for (let i = 0; i < palette.length; i++) {
    let rdiff = (color[0] & 0xff) - (palette[i][0] & 0xff);
    let gdiff = (color[1] & 0xff) - (palette[i][1] & 0xff);
    let bdiff = (color[2] & 0xff) - (palette[i][2] & 0xff);
    let distanceSquared = rdiff * rdiff + gdiff * gdiff + bdiff * bdiff;
    if (distanceSquared < minDistanceSquared) {
      minDistanceSquared = distanceSquared;
      bestIndex = i;
    }
  }
  return palette[bestIndex];

}


function updatePixel(imageData, index, color) {
  imageData[index] = color[0];
  imageData[index + 1] = color[1];
  imageData[index + 2] = color[2];
  imageData[index + 3] = color[3];
}

function getColorErr(color1, color2, rate) {
  const res = [];
  for (let i = 0; i < 3; i++) {
    res.push(Math.floor((color1[i] - color2[i]) / rate));
  }
  return res;
}

function updatePixelErr(imageData, index, err, rate) {
  imageData[index] += err[0] * rate;
  imageData[index + 1] += err[1] * rate;
  imageData[index + 2] += err[2] * rate;
}


function dithering2color(imageData, mode, threshold) {
  const bayerThresholdMap = [
    [15, 135, 45, 165],
    [195, 75, 225, 105],
    [60, 180, 30, 150],
    [240, 120, 210, 90]
  ];

  const lumR = [];
  const lumG = [];
  const lumB = [];
  for (let i = 0; i < 256; i++) {
    lumR[i] = i * 0.299;
    lumG[i] = i * 0.587;
    lumB[i] = i * 0.114;
  }
  // const imageData = ctx.getImageData(0, 0, width, height);

  const imageDataLength = imageData.data.length;

  // Greyscale luminance (sets r pixels to luminance of rgb)
  for (let i = 0; i <= imageDataLength; i += 4) {
    imageData.data[i] = Math.floor(lumR[imageData.data[i]] + lumG[imageData.data[i + 1]] + lumB[imageData.data[i + 2]]);
  }


  const w = imageData.width;
  let newPixel, err;

  for (let currentPixel = 0; currentPixel <= imageDataLength; currentPixel += 4) {

    if (mode === "level") {
      // No dithering
      imageData.data[currentPixel] = imageData.data[currentPixel] < (threshold + 5) / 100 * 255 ? 0 : 255;
    } else if (mode === "bayer") {
      // 4x4 Bayer ordered dithering algorithm
      var x = currentPixel / 4 % w;
      var y = Math.floor(currentPixel / 4 / w);
      var map = Math.floor((imageData.data[currentPixel] + bayerThresholdMap[x % 4][y % 4]) / 2);
      imageData.data[currentPixel] = (map < (threshold + 5) / 100 * 255) ? 0 : 255;
    } else if (mode === "floydsteinberg") {
      // Floyda€"Steinberg dithering algorithm
      newPixel = imageData.data[currentPixel] < (threshold + 5) / 100 * 255 ? 0 : 255;
      err = Math.floor((imageData.data[currentPixel] - newPixel) / 16);
      imageData.data[currentPixel] = newPixel;

      imageData.data[currentPixel + 4] += err * 7;
      imageData.data[currentPixel + 4 * w - 4] += err * 3;
      imageData.data[currentPixel + 4 * w] += err * 5;
      imageData.data[currentPixel + 4 * w + 4] += err * 1;
    } else if (mode == "Atkinson") {
      // Bill Atkinson's dithering algorithm
      newPixel = imageData.data[currentPixel] < (threshold + 5) / 100 * 255 ? 0 : 255;
      err = Math.floor((imageData.data[currentPixel] - newPixel) / 8);
      imageData.data[currentPixel] = newPixel;
      imageData.data[currentPixel + 4] += err;
      imageData.data[currentPixel + 8] += err;
      imageData.data[currentPixel + 4 * w - 4] += err;
      imageData.data[currentPixel + 4 * w] += err;
      imageData.data[currentPixel + 4 * w + 4] += err;
      imageData.data[currentPixel + 8 * w] += err;
    } else if (mode = 'sketch') {
      // data = imageData.data,
      let color1ToRGB = hexToRGB("#ffffff");
      let color2ToRGB = hexToRGB("#000000");
      let inHSL = RGBToHSL(imageData.data[currentPixel], imageData.data[currentPixel + 1], imageData.data[currentPixel + 2]),
        // BWLight = Math.round(inHSL.l / 100),
        BWLight = inHSL.l < (threshold + 5) ? 0 : 100,
        newR = 0;
      // newG = 0,
      // newB = 0;
      if (BWLight) {
        newR = color1ToRGB.r;
        // newG = color1ToRGB.g;
        // newB = color1ToRGB.b;
      } else {
        newR = color2ToRGB.r;
        // newG = color2ToRGB.g;
        // newB = color2ToRGB.b;
      }
      imageData.data[currentPixel] = newR;
      // imageData.data[currentPixel + 1] = newG;
      // imageData.data[currentPixel + 2] = newB;
      // console.log(newR, newG, newB)
    }
    // Set g and b pixels equal to r
    imageData.data[currentPixel + 1] = imageData.data[currentPixel + 2] = imageData.data[currentPixel];
  }

  return imageData;
}



function dithering3color(imageData, type) {
  var palette = bwrPalette;

  const w = imageData.width;

  for (let currentPixel = 0; currentPixel <= imageData.data.length; currentPixel += 4) {
    const newColor = getNearColorV2(imageData.data.slice(currentPixel, currentPixel + 4), palette);

    if (type === "bwr_floydsteinberg") {
      const err = getColorErr(imageData.data.slice(currentPixel, currentPixel + 4), newColor, 16);

      updatePixel(imageData.data, currentPixel, newColor);
      updatePixelErr(imageData.data, currentPixel + 4, err, 7);
      updatePixelErr(imageData.data, currentPixel + 4 * w - 4, err, 3);
      updatePixelErr(imageData.data, currentPixel + 4 * w, err, 5);
      updatePixelErr(imageData.data, currentPixel + 4 * w + 4, err, 1);
    } else if (type === "bwr_Atkinson") {
      const err = getColorErr(imageData.data.slice(currentPixel, currentPixel + 4), newColor, 8);

      updatePixel(imageData.data, currentPixel, newColor);
      updatePixelErr(imageData.data, currentPixel + 4, err, 1);
      updatePixelErr(imageData.data, currentPixel + 8, err, 1);
      updatePixelErr(imageData.data, currentPixel + 4 * w - 4, err, 1);
      updatePixelErr(imageData.data, currentPixel + 4 * w, err, 1);
      updatePixelErr(imageData.data, currentPixel + 4 * w + 4, err, 1);
      updatePixelErr(imageData.data, currentPixel + 8 * w, err, 1);
    } else if (type == "bwr_level") {
      updatePixel(imageData.data, currentPixel, newColor);
    }
  }
  return imageData

}

function dithering_grey(imageData, mode) {

  const lumR = [];
  const lumG = [];
  const lumB = [];
  for (let i = 0; i < 256; i++) {
    lumR[i] = i * 0.299;
    lumG[i] = i * 0.587;
    lumB[i] = i * 0.114;
  }
  // const imageData = ctx.getImageData(0, 0, width, height);

  const imageDataLength = imageData.data.length;

  // Greyscale luminance (sets r pixels to luminance of rgb)
  for (let i = 0; i <= imageDataLength; i += 4) {
    imageData.data[i] = Math.floor(lumR[imageData.data[i]] + lumG[imageData.data[i + 1]] + lumB[imageData.data[i + 2]]);
  }


  const w = imageData.width;
  let newPixel, err;

  // console.log("imageData")
  // console.log(imageData.data)


  for (let currentPixel = 0; currentPixel <= imageDataLength; currentPixel += 4) {

    if (mode === "grey_4bit") {
      imageData.data[currentPixel] = Math.round(imageData.data[currentPixel] / 85) * 85
    } else if (mode === "grey_16bit") {
      imageData.data[currentPixel] = Math.round(imageData.data[currentPixel] / 17) * 17
    } 
    // else if (mode === "grey_8bit") {
    //   imageData.data[currentPixel] = Math.round(Math.round(imageData.data[currentPixel] * 7 / 255) * 255 / 7)
    // }
    // Set g and b pixels equal to r
    imageData.data[currentPixel + 1] = imageData.data[currentPixel + 2] = imageData.data[currentPixel];
  }
  // console.log("imageData")
  // console.log(imageData.data)
  return imageData;
}

const bwrGrey4bitPalette = [
  [0, 0, 0, 255],
  [255, 255, 255, 255],
  [255 / 3, 255 / 3, 255 / 3, 255],
  [255 / 3 * 2, 255 / 3 * 2, 255 / 3 * 2, 255],
  [255, 0, 0, 255],
  [255, 255 / 3, 255 / 3, 255],
  [255, 255 / 3 * 2, 255 / 3 * 2, 255]
]

const bwrGrey8bitPalette = [
  [0, 0, 0, 255],
  [255, 255, 255, 255],
  [36, 36, 36, 255],
  [73, 73, 73, 255],
  [109, 109, 109, 255],
  [146, 146, 146, 255],
  [182, 182, 182, 255],
  [219, 219, 219, 219],
  [255, 0, 0, 255],
  [255, 36, 36, 255],
  [255, 73, 73, 255],
  [255, 109, 109, 255],
  [255, 146, 146, 255],
  [255, 182, 182, 255],
  [255, 219, 219, 219],
]



function dithering_grey_bwr(imageData, mode) {
  const imageDataLength = imageData.data.length;
  var palette
  if (mode == 'grey_bwr_4bit')
    palette = bwrGrey4bitPalette;
  else if (mode == 'grey_bwr_8bit')
    palette = bwrGrey8bitPalette;
  const w = imageData.width;
  for (let currentPixel = 0; currentPixel <= imageData.data.length; currentPixel += 4) {
    const newColor = getNearColorV2(imageData.data.slice(currentPixel, currentPixel + 4), palette);

    updatePixel(imageData.data, currentPixel, newColor);

  }




  return imageData;
}


function convert_dithering(imageData, mode, threshold) {

  if (mode.startsWith('bwr')) {
    imageData = dithering3color(imageData, mode)
  } else if (mode.startsWith('grey_bwr')) {
    imageData = dithering_grey_bwr(imageData, mode)
  } else if (mode.startsWith('grey')) {
    imageData = dithering_grey(imageData, mode)
  } else {
    imageData = dithering2color(imageData, mode, threshold);
  }
  return imageData
}

function bitmap_index(i, j, w) {
  return 4 * w * j + 4 * i;
}

function rotateImage(image_old, direction) {

  let width_temp = (direction & 1) ? image_old.height : image_old.width;
  let height_temp = (direction & 1) ? image_old.width : image_old.height;
  const image_new = {
    data: new Uint8ClampedArray(width_temp * height_temp * 4),
    width: width_temp,
    height: height_temp
  };

  for (let j = 0; j < image_old.height; j++) {
    for (let i = 0; i < image_old.width; i++) {
      for (let k = 0; k < 4; k++) {
        switch (direction) {
          case 0:
            image_new.data[bitmap_index(i, j, image_new.width) + k] = image_old.data[bitmap_index(i, j, image_old.width) + k];
            break;
          case 1:
            image_new.width = image_old.height;
            image_new.height = image_old.width;
            image_new.data[bitmap_index(j, image_old.width - i - 1, image_new.width) + k] = image_old.data[bitmap_index(i, j, image_old.width) + k];
            break;
          case 2:
            image_new.data[bitmap_index(image_old.width - i - 1, image_old.height - j - 1, image_new.width) + k] = image_old.data[bitmap_index(i, j, image_old.width) + k];
            break;
          case 3:
            image_new.width = image_old.height;
            image_new.height = image_old.width;
            image_new.data[bitmap_index(image_old.height - j - 1, i, image_new.width) + k] = image_old.data[bitmap_index(i, j, image_old.width) + k];
            break;
        }

      }
    }
  }
  return image_new;
}

function run_length_encoding(data) {
  let new_data = []
  let num = 1
  let temp = data[0]
  for (let i = 0; i < data.length; i++) {
    if ((i < data.length - 1) && (data[i + 1] == temp) && (num != 255)) {
      num++;
    } else {
      new_data.push(num, temp)
      temp = data[i + 1]
      num = 1
    }
  }
  return new_data
}

function find_repeat_times(data, start) {
  let num = 1
  if (start == data.length - 1) { return 1 }
  for (let i = start; i < data.length - 1; i++) {
    if (data[i] == data[i + 1]) {
      num++
      if (i == data.length - 2) {
        return num
      }
    }
    else {
      return num
    }
  }
}
function run_length_encoding2(data) {
  let new_data_temp = []
  let new_data = []
  for (let i = 0; i < data.length; i++) {
    new_data_temp.push(find_repeat_times(data, i))
  }
  // console.log(new_data_temp)
  let pos = 0

  while (pos < data.length) {
    if (new_data_temp[pos] > 1) {
      // console.log(pos)
      let repeat_times = new_data_temp[pos]
      while (repeat_times > 0) {
        // let num = Math.min(10, repeat_times)//max set 127。。。。40@64mhz，15@16mhz
        let num = Math.min(127, repeat_times)//max set 127。。。。40@64mhz，15@16mhz
        repeat_times -= num
        new_data.push(num, data[pos])
        pos += num
      }
    }
    else {
      let temp_times = find_repeat_times(new_data_temp, pos)
      // console.log(temp_times)  
      while (temp_times > 0) {
        let num = Math.min(127, temp_times)
        temp_times -= num
        // console.log(num,0x80|num)
        new_data.push(0x80 | num)
        for (let i = 0; i < num; i++) {
          new_data.push(data[pos + i])
        }
        pos += num
      }
    }
  }
  return new_data
}


function canvas2bytes(canvas, type = 'bw', rotation) {
  // console.log('---')
  const ctx = canvas.getContext("2d");
  const imageData_temp = ctx.getImageData(0, 0, canvas.width, canvas.height);


  const imageData = new rotateImage(imageData_temp, (4 - (rotation) & 3));

  const arr = [];
  let buffer = [];

  for (let y = 0; y < imageData.height; y++) {
    for (let x = 0; x < imageData.width; x++) {
      const index = (imageData.width * 4 * y) + x * 4;
      if (type == 'bwr') {
        buffer.push(imageData.data[index] > 0 && imageData.data[index + 1] === 0 && imageData.data[index + 2] === 0 ? 1 : 0);
      } else if (type == 'grey_4bit') {
        buffer.push(((imageData.data[index] / 85) >> 1));
        buffer.push(((imageData.data[index] / 85) & 1));
      } else if (type == 'grey_8bit') {
        buffer.push((Math.round(imageData.data[index] * 7 / 255) >> 2) & 1);
        buffer.push((Math.round(imageData.data[index] * 7 / 255) >> 1) & 1);
        buffer.push(Math.round(imageData.data[index] * 7 / 255) & 1);
      }
      else if (type == 'grey_16bit') {
        buffer.push((imageData.data[index] / 17) >> 3);
        buffer.push(((imageData.data[index] / 17) >> 2) & 1);
        buffer.push(((imageData.data[index] / 17) >> 1) & 1);
        buffer.push((imageData.data[index] / 17) & 1);
      } 
      else if (type == 'grey_bwr_8bit') {
        if (imageData.data[index] == imageData.data[index + 1]) {
          buffer.push(0);//black
          buffer.push((Math.round(imageData.data[index] * 7 / 255) >> 2) & 1);
          buffer.push((Math.round(imageData.data[index] * 7 / 255) >> 1) & 1);
          buffer.push(Math.round(imageData.data[index] * 7 / 255) & 1);
        }
        else {
          buffer.push(1);//red
          buffer.push((Math.round((255 - imageData.data[index + 1]) * 7 / 255) >> 2) & 1);
          buffer.push((Math.round((255 - imageData.data[index + 1])* 7 / 255) >> 1) & 1);
          buffer.push(Math.round((255 - imageData.data[index + 1] )* 7 / 255) & 1);
        }
      }





      else {
        buffer.push(imageData.data[index] > 0 && imageData.data[index + 1] > 0 && imageData.data[index + 2] > 0 ? 1 : 0);
      }

      if (buffer.length === 8) {
        arr.push(parseInt(buffer.join(''), 2));
        buffer = [];
      }
    }
  }
  console.log(arr.length)
  console.log(arr)
  return arr;
}

function bytesToHex2(data) {
  return new Uint8Array(data).reduce(
    function (memo, i) {
      return memo + '0x' + ("0" + i.toString(16)).slice(-2) + ',';
    }, "");
}


module.exports = {
  convert_dithering,
  canvas2bytes,
  run_length_encoding,
  run_length_encoding2,
}